﻿using System;
using System.Net;
using System.Text;
using System.Threading;
using BSDS.Core.Infrastructure.Logging;
using BSDS.Core.Model.Services.Geocoding;
using BSDS.Core.Model.Services.Jobs.Core;
using BSDS.Core.Model.Services.Uploading;

namespace BSDS.Core.Model.Services.Jobs
{
    public class JobService
    {
        private const string LogTemplate = "Error querying job status. \n {0} \n {1}";

        private readonly JobRepository repository;
        private readonly GeocodingService geocodingService;
        private readonly UploadService uploadService;
        private readonly DownloadService downloadService;
        private readonly Logger logger;

        public JobService(JobRepository repository, GeocodingService geocodingService, 
            UploadService uploadService, DownloadService downloadService, Logger logger)
        {
            this.repository = repository;
            this.geocodingService = geocodingService;
            this.uploadService = uploadService;
            this.downloadService = downloadService;
            this.logger = logger;
        }

        public string GetShortDetailsMessage(string jobId)
        {
            return ExecuteForJob(jobId, CreateShortDetailsMessage);
        }

        public string GetLongDetailsMessage(string jobId)
        {
            return ExecuteForJob(jobId, CreateLongDetailsMessage);
        }

        private string ExecuteForJob(string jobId, Func<Job, IJobStateProvider, string> function)
        {
            if (repository.Exists(jobId, GeocodeJob.TypeName))
            {
                return function(repository.Get<GeocodeJob>(jobId), geocodingService);
            }

            if (repository.Exists(jobId, UploadJob.TypeName))
            {
                return function(repository.Get<UploadJob>(jobId), uploadService);
            }

            if (repository.Exists(jobId, DownloadJob.TypeName))
            {
                return function(repository.Get<DownloadJob>(jobId), downloadService);
            }

            throw new ArgumentException("No such job " + jobId);
        }

        private string CreateLongDetailsMessage(Job job, IJobStateProvider stateProvider)
        {
            var state = stateProvider.QueryState(job.Id);

            var messageBuilder = new StringBuilder();
            messageBuilder.Append(job.ToString()).Append(Environment.NewLine)
                          .Append("Job Status: ").Append(state.Status);

            if (state.Status != JobStatus.Pending)
            {
                messageBuilder.Append(Environment.NewLine).Append("Job Completion Status: ")
                              .Append(state.EndResult.ComplitionStatus);
            }

            return messageBuilder.ToString();
        }

        private string CreateShortDetailsMessage(Job job, IJobStateProvider stateProvider)
        {
            var state = stateProvider.QueryState(job.Id);

            if (state.Status == JobStatus.Pending)
            {
                const string pendingTemplate = "{0} Job {1}: {2} since {3} ({4})";

                var pendingMessage = string.Format(pendingTemplate, job.JobTypeName, job.Id, JobStatus.Pending, 
                    job.SubmissionDate, GetTimeBetween(job.SubmissionDate, DateTime.Now));

                return pendingMessage;
            }

            const string completedTemplate = "{0} Job {1}: {2}";

            var completedMessage = string.Format(completedTemplate, job.JobTypeName, job.Id, state.EndResult.ComplitionStatus);

            if (state.CompletedDate.HasValue)
            {
                var totalTime = GetTimeBetween(job.SubmissionDate, state.CompletedDate.Value);
                completedMessage += " (total time: " + totalTime + " )";
            }

            return completedMessage;

        }

        private string GetTimeBetween(DateTime startTime, DateTime endTime)
        {
            var period = endTime.Subtract(startTime);
            const string shortTemplate = "{0}hrs {1}min {2}sec";
            const string longTemplate = "{0}days {1}hrs {2}min {3}sec";

            if (period.Days > 0)
            {
                return string.Format(longTemplate, period.Days, period.Hours, 
                    period.Minutes, period.Seconds);
            }

            return string.Format(shortTemplate, period.Hours, 
                period.Minutes, period.Seconds);
        }

        public JobState WaitForEnd(string jobId, IJobStateProvider stateProvider)
        {
            logger.Log("Waiting for status ...");

            var firstCheck = true;
            JobState state = null;
            do
            {
                if (!firstCheck)
                {
                    Thread.Sleep(TimeSpan.FromSeconds(20));
                }
                else
                {
                    firstCheck = false;
                }

                try
                {
                    state = stateProvider.QueryState(jobId);

                    logger.Log(state.Status.ToString());
                }
                catch (WebException webException)
                {
                    logger.Log(string.Format(LogTemplate, webException.Message, webException.StackTrace));
                }
                catch (Exception e)
                {
                    logger.Log(string.Format(LogTemplate, e.Message, e.StackTrace));
                    throw;
                }
            }
            while (state == null || state.Status == JobStatus.Pending);

            return state;
        }
    }
}